{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": [],
      "toc_visible": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "source": [
        "# Convolutional Neural Networks from Scratch\n",
        "\n",
        "In this notebook we are going to implement and train a convolutional neural network from scratch using only numpy!\n",
        "\n",
        "\n",
        "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/SharifiZarchi/Introduction_to_Machine_Learning/blob/main/Jupyter_Notebooks/Chapter_04_Computer_Vision/CNNs_from_scratch.ipynb)\n",
        "[![Open In kaggle](https://kaggle.com/static/images/open-in-kaggle.svg)](https://kaggle.com/kernels/welcome?src=https://raw.githubusercontent.com/SharifiZarchi/Introduction_to_Machine_Learning/main/Jupyter_Notebooks/Chapter_04_Computer_Vision/CNNs_from_scratch.ipynb)"
      ],
      "metadata": {
        "id": "Ecaqr9YkWpbL"
      }
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {
        "id": "PqFOwWLZVPI0",
        "cellView": "form"
      },
      "outputs": [],
      "source": [
        "# @title imports\n",
        "\n",
        "import numpy as np\n",
        "from matplotlib import pyplot as plt\n",
        "from sklearn.datasets import fetch_openml\n",
        "from sklearn.model_selection import train_test_split\n",
        "from sklearn.metrics import confusion_matrix\n",
        "from tqdm import trange"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# @title NNs from scratch\n",
        "\n",
        "class Layer:\n",
        "    def __init__(self):\n",
        "        self.inp = None\n",
        "        self.out = None\n",
        "\n",
        "    def __call__(self, inp: np.ndarray) -> np.ndarray:\n",
        "        return self.forward(inp)\n",
        "\n",
        "    def forward(self, inp: np.ndarray) -> np.ndarray:\n",
        "        raise NotImplementedError\n",
        "\n",
        "    def backward(self, up_grad: np.ndarray) -> np.ndarray:\n",
        "        raise NotImplementedError\n",
        "\n",
        "    def step(self, lr: float) -> None:\n",
        "        pass\n",
        "\n",
        "\n",
        "class Linear(Layer):\n",
        "    def __init__(self, in_dim: int, out_dim: int):\n",
        "        super().__init__()\n",
        "        # He initialization: better scaling for deep networks\n",
        "        self.w = 0.1 * np.random.randn(in_dim, out_dim)\n",
        "        self.b = np.zeros((1, out_dim))\n",
        "        self.dw = np.zeros_like(self.w)\n",
        "        self.db = np.zeros_like(self.b)\n",
        "\n",
        "    def forward(self, inp: np.ndarray) -> np.ndarray:\n",
        "        \"\"\"Perform the linear transformation: output = inp * W + b\"\"\"\n",
        "        self.inp = inp\n",
        "        self.out = np.dot(inp, self.w) + self.b\n",
        "        return self.out\n",
        "\n",
        "    def backward(self, up_grad: np.ndarray) -> np.ndarray:\n",
        "        \"\"\"Backpropagate the gradients through this layer.\"\"\"\n",
        "        # Compute gradients for weights and biases\n",
        "        self.dw = np.dot(self.inp.T, up_grad)  # Gradient wrt weights\n",
        "        self.db = np.sum(up_grad, axis=0, keepdims=True)  # Gradient wrt biases\n",
        "        # Compute gradient to propagate back (downstream)\n",
        "        down_grad = np.dot(up_grad, self.w.T)\n",
        "        return down_grad\n",
        "\n",
        "    def step(self, lr: float) -> None:\n",
        "        \"\"\"Update the weights and biases using the gradients.\"\"\"\n",
        "        self.w -= lr * self.dw\n",
        "        self.b -= lr * self.db\n",
        "\n",
        "\n",
        "class ReLU(Layer):\n",
        "    def forward(self, inp: np.ndarray) -> np.ndarray:\n",
        "        \"\"\"ReLU Activation: f(x) = max(0, x)\"\"\"\n",
        "        self.inp = inp\n",
        "        self.out = np.maximum(0, inp)\n",
        "        return self.out\n",
        "\n",
        "    def backward(self, up_grad: np.ndarray) -> np.ndarray:\n",
        "        \"\"\"Backward pass for ReLU: derivative is 1 where input > 0, else 0.\"\"\"\n",
        "        down_grad = up_grad * (self.inp > 0)  # Efficient boolean indexing\n",
        "        return down_grad\n",
        "\n",
        "\n",
        "class Softmax(Layer):\n",
        "    def forward(self, inp: np.ndarray) -> np.ndarray:\n",
        "        \"\"\"Softmax Activation: f(x) = exp(x) / sum(exp(x))\"\"\"\n",
        "        # Subtract max for numerical stability\n",
        "        exp_values = np.exp(inp - np.max(inp, axis=1, keepdims=True))\n",
        "        self.out = exp_values / np.sum(exp_values, axis=1, keepdims=True)\n",
        "        return self.out\n",
        "\n",
        "    def backward(self, up_grad: np.ndarray) -> np.ndarray:\n",
        "        \"\"\"Backward pass for Softmax using the Jacobian matrix.\"\"\"\n",
        "        down_grad = np.empty_like(up_grad)\n",
        "        for i in range(up_grad.shape[0]):\n",
        "            single_output = self.out[i].reshape(-1, 1)\n",
        "            jacobian = np.diagflat(single_output) - np.dot(single_output, single_output.T)\n",
        "            down_grad[i] = np.dot(jacobian, up_grad[i])\n",
        "        return down_grad\n",
        "\n",
        "\n",
        "class Loss:\n",
        "    def __init__(self):\n",
        "        self.prediction = None\n",
        "        self.target = None\n",
        "        self.loss = None\n",
        "\n",
        "    def __call__(self, prediction: np.ndarray, target: np.ndarray) -> float:\n",
        "        return self.forward(prediction, target)\n",
        "\n",
        "    def forward(self, prediction: np.ndarray, target: np.ndarray) -> float:\n",
        "        raise NotImplementedError\n",
        "\n",
        "    def backward(self) -> np.ndarray:\n",
        "        raise NotImplementedError\n",
        "\n",
        "\n",
        "class CrossEntropy(Loss):\n",
        "    def forward(self, prediction: np.ndarray, target: np.ndarray) -> float:\n",
        "        \"\"\"Cross-Entropy Loss for classification.\"\"\"\n",
        "        self.prediction = prediction\n",
        "        self.target = target\n",
        "        # Clip predictions to avoid log(0)\n",
        "        clipped_pred = np.clip(prediction, 1e-12, 1.0)\n",
        "        # Compute and return the loss\n",
        "        self.loss = -np.mean(np.sum(target * np.log(clipped_pred), axis=1))\n",
        "        return self.loss\n",
        "\n",
        "    def backward(self) -> np.ndarray:\n",
        "        \"\"\"Gradient of Cross-Entropy Loss.\"\"\"\n",
        "        # Gradient wrt prediction (assuming softmax and one-hot targets)\n",
        "        grad = -self.target / self.prediction / self.target.shape[0]\n",
        "        return grad\n",
        "\n",
        "\n",
        "class CNN:\n",
        "    def __init__(self, layers: list[Layer], loss_fn: Loss, lr: float) -> None:\n",
        "        \"\"\"\n",
        "        Multi-Layer Perceptron (MLP) class.\n",
        "        Arguments:\n",
        "        - layers: List of layers (e.g., Linear, ReLU, etc.).\n",
        "        - loss_fn: Loss function object (e.g., CrossEntropy, MSE).\n",
        "        - lr: Learning rate.\n",
        "        \"\"\"\n",
        "        self.layers = layers\n",
        "        self.loss_fn = loss_fn\n",
        "        self.lr = lr\n",
        "\n",
        "    def __call__(self, inp: np.ndarray) -> np.ndarray:\n",
        "        \"\"\"Makes the model callable, equivalent to forward pass.\"\"\"\n",
        "        return self.forward(inp)\n",
        "\n",
        "    def forward(self, inp: np.ndarray) -> np.ndarray:\n",
        "        \"\"\"Pass input through each layer sequentially.\"\"\"\n",
        "        for layer in self.layers:\n",
        "            inp = layer.forward(inp)\n",
        "        return inp\n",
        "\n",
        "    def loss(self, prediction: np.ndarray, target: np.ndarray) -> float:\n",
        "        \"\"\"Calculate the loss.\"\"\"\n",
        "        return self.loss_fn(prediction, target)\n",
        "\n",
        "    def backward(self) -> None:\n",
        "        \"\"\"Perform backpropagation by propagating the gradient backwards through the layers.\"\"\"\n",
        "        up_grad = self.loss_fn.backward()\n",
        "        for layer in reversed(self.layers):\n",
        "            up_grad = layer.backward(up_grad)\n",
        "\n",
        "    def update(self) -> None:\n",
        "        \"\"\"Update the parameters of each layer using the gradients and the learning rate.\"\"\"\n",
        "        for layer in self.layers:\n",
        "            layer.step(self.lr)\n",
        "\n",
        "    def train(self, x_train: np.ndarray, y_train: np.ndarray, epochs: int, batch_size: int) -> np.ndarray:\n",
        "        \"\"\"Train the MLP over the given dataset for a number of epochs.\"\"\"\n",
        "        losses = np.empty(epochs)\n",
        "        for epoch in (pbar := trange(epochs)):\n",
        "            running_loss = 0.0\n",
        "            for i in range(0, len(x_train), batch_size):\n",
        "                x_batch = x_train[i:i + batch_size]\n",
        "                y_batch = y_train[i:i + batch_size]\n",
        "\n",
        "                # Forward pass\n",
        "                prediction = self.forward(x_batch)\n",
        "\n",
        "                # Compute loss\n",
        "                running_loss += self.loss(prediction, y_batch) * batch_size\n",
        "\n",
        "                # Backward pass\n",
        "                self.backward()\n",
        "\n",
        "                # Update parameters\n",
        "                self.update()\n",
        "\n",
        "            # Normalize running loss by total number of samples\n",
        "            running_loss /= len(x_train)\n",
        "            pbar.set_description(f\"Loss: {running_loss:.3f}\")\n",
        "            losses[epoch] = running_loss\n",
        "\n",
        "        return losses"
      ],
      "metadata": {
        "cellView": "form",
        "id": "zN_fZUEhqxol"
      },
      "execution_count": 2,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# @title helper functions\n",
        "\n",
        "def plot_training(losses):\n",
        "    # Plot the loss\n",
        "    plt.plot(losses)\n",
        "    plt.title(\"Training loss\")\n",
        "    plt.xlabel(\"Epoch\")\n",
        "    plt.ylabel(\"Loss\")\n",
        "    plt.show()\n",
        "\n",
        "\n",
        "def plot_confusion_matrix(y_true, y_pred, class_names, kept_classes):\n",
        "    dim = len(kept_classes)\n",
        "    labels = [class_names[i] for i in kept_classes]\n",
        "    # Plot the confusion matrix\n",
        "    conf_mat = confusion_matrix(y_true, y_pred)\n",
        "    norm_conf_mat = conf_mat / np.sum(conf_mat, axis=1)\n",
        "    # plot the matrix\n",
        "    fig, ax = plt.subplots()\n",
        "    plt.imshow(norm_conf_mat)\n",
        "    plt.title('Confusion Matrix')\n",
        "    plt.xlabel('Predictions')\n",
        "    plt.ylabel('Labels')\n",
        "    plt.xticks(range(dim), labels, rotation=45)\n",
        "    plt.yticks(range(dim), labels)\n",
        "    plt.colorbar()\n",
        "    # Put number of each cell in plot\n",
        "    for i in range(dim):\n",
        "        for j in range(dim):\n",
        "            c = conf_mat[j, i]\n",
        "            color = 'black' if c > 500 else 'white'\n",
        "            ax.text(i, j, str(int(c)), va='center', ha='center', color=color)\n",
        "    plt.show()\n",
        "\n",
        "\n",
        "def get_data(filter_classes):\n",
        "    fashion_mnist = fetch_openml(\"Fashion-MNIST\", parser='auto')\n",
        "    x, y = fashion_mnist['data'], fashion_mnist['target'].astype(int)\n",
        "    # Remove classes\n",
        "    filtered_indices = np.isin(y, filter_classes)\n",
        "    x, y = x[filtered_indices].to_numpy(), y[filtered_indices]\n",
        "    # Normalize the pixels to be in [-1, +1] range\n",
        "    x = ((x / 255.) - .5) * 2\n",
        "    removed_class_count = 0\n",
        "    for i in range(10):  # Fix the labels\n",
        "        if i in filter_classes and removed_class_count != 0:\n",
        "            y[y == i] = i - removed_class_count\n",
        "        elif i not in filter_classes:\n",
        "            removed_class_count += 1\n",
        "    # Do the train-test split\n",
        "    return train_test_split(x, y, test_size=10_000)\n",
        "\n",
        "\n",
        "def onehot_encoder(y, num_labels):\n",
        "    one_hot = np.zeros(shape=(y.size, num_labels), dtype=int)\n",
        "    one_hot[np.arange(y.size), y] = 1\n",
        "    return one_hot"
      ],
      "metadata": {
        "id": "9AKM0RgiVSOQ",
        "cellView": "form"
      },
      "execution_count": 3,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "# Layers\n",
        "\n",
        "In this notebook we want to implement `Conv2D`, `MaxPool2D`, `AvgPool2D` and `Flatten` layers and train a CNN.\n",
        "\n"
      ],
      "metadata": {
        "id": "GvFUeXMNadnZ"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "\n",
        "Recall from our notebook [Neural Networks from Scratch](https://colab.research.google.com/github/SharifiZarchi/Introduction_to_Machine_Learning/blob/main/Jupyter_Notebooks/Chapter_03_Neural_Networks/NNs_from_scratch.ipynb) that the abstract `Layer` class is defined as follows:\n",
        "```\n",
        "class Layer:\n",
        "    def __init__(self):\n",
        "        self.inp = None\n",
        "        self.out = None\n",
        "\n",
        "    def __call__(self, inp: np.ndarray) -> np.ndarray:\n",
        "        return self.forward(inp)\n",
        "\n",
        "    def forward(self, inp: np.ndarray) -> np.ndarray:\n",
        "        raise NotImplementedError\n",
        "\n",
        "    def backward(self, up_grad: np.ndarray) -> np.ndarray:\n",
        "        raise NotImplementedError\n",
        "\n",
        "    def step(self, lr: float) -> None:\n",
        "        pass\n",
        "\n",
        "```\n"
      ],
      "metadata": {
        "id": "y3P4ztQQvHeN"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Convolutional Layer\n",
        "Applies convolution operations to input images by sliding a learnable kernel over them, computing dot products with patches of the input. This helps detect spatial features like edges and textures."
      ],
      "metadata": {
        "id": "2G_b2_sAr1In"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "class Conv2D(Layer):\n",
        "    def __init__(self, in_channels: int, out_channels: int, kernel_size: int, stride: int = 1, padding: int = 0):\n",
        "        super().__init__()\n",
        "        self.in_channels = in_channels\n",
        "        self.out_channels = out_channels\n",
        "        self.kernel_size = kernel_size\n",
        "        self.stride = stride\n",
        "        self.padding = padding\n",
        "\n",
        "        # Initialize weights and biases\n",
        "        self.w = 0.1 * np.random.randn(out_channels, in_channels, kernel_size, kernel_size)\n",
        "        self.b = np.zeros((out_channels, 1))\n",
        "\n",
        "    def forward(self, inp: np.ndarray) -> np.ndarray:\n",
        "        self.inp = inp\n",
        "        batch_size, in_channels, height, width = inp.shape\n",
        "        assert in_channels == self.in_channels, \"Input channels must match.\"\n",
        "\n",
        "        # Padding the input\n",
        "        self.padded_inp = np.pad(inp, ((0, 0), (0, 0), (self.padding, self.padding), (self.padding, self.padding)), mode='constant')\n",
        "\n",
        "        # Output dimensions\n",
        "        out_height = (height + 2 * self.padding - self.kernel_size) // self.stride + 1\n",
        "        out_width = (width + 2 * self.padding - self.kernel_size) // self.stride + 1\n",
        "        self.out = np.zeros((batch_size, self.out_channels, out_height, out_width))\n",
        "\n",
        "        # Convolution operation\n",
        "        for i in range(out_height):\n",
        "            for j in range(out_width):\n",
        "                region = self.padded_inp[:, :, i*self.stride:i*self.stride+self.kernel_size, j*self.stride:j*self.stride+self.kernel_size]\n",
        "                self.out[:, :, i, j] = np.tensordot(region, self.w, axes=([1, 2, 3], [1, 2, 3])) + self.b.T\n",
        "\n",
        "        return self.out\n",
        "\n",
        "    def backward(self, up_grad: np.ndarray) -> np.ndarray:\n",
        "        \"\"\"Backward pass for Conv2D layer.\"\"\"\n",
        "        batch_size, in_channels, height, width = self.inp.shape\n",
        "        _, _, out_height, out_width = up_grad.shape\n",
        "\n",
        "        # Initialize gradients\n",
        "        self.dw = np.zeros_like(self.w)\n",
        "        self.db = np.sum(up_grad, axis=(0, 2, 3), keepdims=True).reshape(self.out_channels, 1)\n",
        "        down_grad = np.zeros_like(self.padded_inp)\n",
        "\n",
        "        # Gradient computation for weights and input\n",
        "        for i in range(out_height):\n",
        "            for j in range(out_width):\n",
        "                region = self.padded_inp[:, :, i*self.stride:i*self.stride+self.kernel_size, j*self.stride:j*self.stride+self.kernel_size]\n",
        "                self.dw += np.tensordot(up_grad[:, :, i, j], region, axes=([0], [0]))  # Compute weight gradient\n",
        "                for n in range(batch_size):\n",
        "                    down_grad[n, :, i*self.stride:i*self.stride+self.kernel_size, j*self.stride:j*self.stride+self.kernel_size] += np.tensordot(self.w, up_grad[n, :, i, j], axes=(0, 0))\n",
        "\n",
        "        # Remove padding if applied\n",
        "        if self.padding > 0:\n",
        "            down_grad = down_grad[:, :, self.padding:-self.padding, self.padding:-self.padding]\n",
        "\n",
        "        return down_grad\n",
        "\n",
        "    def step(self, lr: float) -> None:\n",
        "        \"\"\"Update weights and biases.\"\"\"\n",
        "        self.w -= lr * self.dw\n",
        "        self.b -= lr * self.db"
      ],
      "metadata": {
        "id": "4a4HoUpxr29X"
      },
      "execution_count": 4,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Max Pooling Layer\n",
        "Reduces the spatial dimensions of the input by selecting the maximum value from non-overlapping regions (windows) of the input. This helps in downsampling and capturing important features."
      ],
      "metadata": {
        "id": "4KjqIR_Is_Wx"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "class MaxPool2D(Layer):\n",
        "    def __init__(self, pool_size: int = 2, stride: int = 2):\n",
        "        \"\"\"Max Pooling Layer.\"\"\"\n",
        "        super().__init__()\n",
        "        self.pool_size = pool_size\n",
        "        self.stride = stride\n",
        "\n",
        "    def forward(self, inp: np.ndarray) -> np.ndarray:\n",
        "        \"\"\"Forward pass of max pooling.\"\"\"\n",
        "        self.inp = inp\n",
        "        batch_size, channels, height, width = inp.shape\n",
        "\n",
        "        out_height = (height - self.pool_size) // self.stride + 1\n",
        "        out_width = (width - self.pool_size) // self.stride + 1\n",
        "\n",
        "        out = np.zeros((batch_size, channels, out_height, out_width))\n",
        "\n",
        "        for i in range(out_height):\n",
        "            for j in range(out_width):\n",
        "                region = inp[:, :, i*self.stride:i*self.stride+self.pool_size, j*self.stride:j*self.stride+self.pool_size]\n",
        "                out[:, :, i, j] = np.max(region, axis=(2, 3))\n",
        "\n",
        "        self.out = out\n",
        "        return self.out\n",
        "\n",
        "    def backward(self, up_grad: np.ndarray) -> np.ndarray:\n",
        "        \"\"\"Backward pass for max pooling.\"\"\"\n",
        "        batch_size, channels, height, width = self.inp.shape\n",
        "        down_grad = np.zeros_like(self.inp)\n",
        "\n",
        "        out_height, out_width = up_grad.shape[2], up_grad.shape[3]\n",
        "\n",
        "        for i in range(out_height):\n",
        "            for j in range(out_width):\n",
        "                region = self.inp[:, :, i*self.stride:i*self.stride+self.pool_size, j*self.stride:j*self.stride+self.pool_size]\n",
        "                max_mask = (region == np.max(region, axis=(2, 3), keepdims=True))\n",
        "                down_grad[:, :, i*self.stride:i*self.stride+self.pool_size, j*self.stride:j*self.stride+self.pool_size] += max_mask * up_grad[:, :, i, j][:, :, None, None]\n",
        "\n",
        "        return down_grad"
      ],
      "metadata": {
        "id": "x4kAUQ17s3c2"
      },
      "execution_count": 5,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Average Pooling Layer\n",
        "Similar to MaxPooling, but instead of taking the maximum value, it averages the values in each window. This smoothens the features while reducing spatial dimensions."
      ],
      "metadata": {
        "id": "toBCxhwTs8Br"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "class AvgPool2D(Layer):\n",
        "    def __init__(self, pool_size: int = 2, stride: int = 2):\n",
        "        \"\"\"Average Pooling Layer.\"\"\"\n",
        "        super().__init__()\n",
        "        self.pool_size = pool_size\n",
        "        self.stride = stride\n",
        "\n",
        "    def forward(self, inp: np.ndarray) -> np.ndarray:\n",
        "        \"\"\"Forward pass of average pooling.\"\"\"\n",
        "        self.inp = inp\n",
        "        batch_size, channels, height, width = inp.shape\n",
        "\n",
        "        out_height = (height - self.pool_size) // self.stride + 1\n",
        "        out_width = (width - self.pool_size) // self.stride + 1\n",
        "\n",
        "        out = np.zeros((batch_size, channels, out_height, out_width))\n",
        "\n",
        "        for i in range(out_height):\n",
        "            for j in range(out_width):\n",
        "                region = inp[:, :, i*self.stride:i*self.stride+self.pool_size, j*self.stride:j*self.stride+self.pool_size]\n",
        "                out[:, :, i, j] = np.mean(region, axis=(2, 3))\n",
        "\n",
        "        self.out = out\n",
        "        return self.out\n",
        "\n",
        "    def backward(self, up_grad: np.ndarray) -> np.ndarray:\n",
        "        \"\"\"Backward pass for average pooling.\"\"\"\n",
        "        batch_size, channels, height, width = self.inp.shape\n",
        "        down_grad = np.zeros_like(self.inp)\n",
        "\n",
        "        out_height, out_width = up_grad.shape[2], up_grad.shape[3]\n",
        "\n",
        "        for i in range(out_height):\n",
        "            for j in range(out_width):\n",
        "                region_grad = up_grad[:, :, i, j][:, :, None, None] / (self.pool_size * self.pool_size)\n",
        "                down_grad[:, :, i*self.stride:i*self.stride+self.pool_size, j*self.stride:j*self.stride+self.pool_size] += region_grad\n",
        "\n",
        "        return down_grad"
      ],
      "metadata": {
        "id": "O3C5PnIAs4d5"
      },
      "execution_count": 6,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Flatten\n",
        "Reshapes a multi-dimensional input (like an image) into a single vector. This is typically used to transition from convolutional layers to fully connected layers in a neural network."
      ],
      "metadata": {
        "id": "FF2tcD4Is41v"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "class Flatten(Layer):\n",
        "    def forward(self, inp: np.ndarray) -> np.ndarray:\n",
        "        \"\"\"Flatten the input into a 2D array.\"\"\"\n",
        "        self.inp_shape = inp.shape\n",
        "        return inp.reshape(self.inp_shape[0], -1)\n",
        "\n",
        "    def backward(self, up_grad: np.ndarray) -> np.ndarray:\n",
        "        \"\"\"Reshape the gradient back to the original input shape.\"\"\"\n",
        "        return up_grad.reshape(self.inp_shape)"
      ],
      "metadata": {
        "id": "aomYpXp8s4US"
      },
      "execution_count": 7,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "# Training"
      ],
      "metadata": {
        "id": "AzONLtl5kc8W"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Loading the Fashion-MNIST Dataset"
      ],
      "metadata": {
        "id": "r93pdZdrZ77B"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "For simplicity you can use `get_data` to load the Fashion-MNIST dataset. Since we aren't using GPUs, in order to save time and get better results, we are only going to include 3 classes in our training. However you can easily modify this cell to include different classes."
      ],
      "metadata": {
        "id": "bOCW3Kh9azY6"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "class_names = {0: 'T-shirt/top', 1: 'Trouser', 2: 'Pullover',\n",
        "               3: 'Dress', 4: 'Coat', 5:  'Sandal', 6: 'Shirt',\n",
        "               7: 'Sneaker', 8: 'Bag', 9: 'Ankle boot'}\n",
        "\n",
        "# Include all the classes you want to see in training\n",
        "kept_classes = [0, 1, 7]  # T-shirt/top, Trouser, Sneaker\n",
        "\n",
        "# Download the dataset and split it into training and testing sets\n",
        "x_train, x_test, y_train, y_test = get_data(kept_classes)\n",
        "\n",
        "# Reshape the images for the convolutional neural network\n",
        "x_train = x_train.reshape(-1, 1, 28, 28)\n",
        "x_test = x_test.reshape(-1, 1, 28, 28)\n",
        "\n",
        "# One-hot encode the target labels of the training set\n",
        "y_train = onehot_encoder(y_train, num_labels=len(kept_classes))"
      ],
      "metadata": {
        "id": "xmRyKM-oZ-hh"
      },
      "execution_count": 8,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Training the Network"
      ],
      "metadata": {
        "id": "sZeNgQXybi4P"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "Now we can define the network and train it on the dataset."
      ],
      "metadata": {
        "id": "2qfmP-hYkpfz"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Define the layers of the neural network\n",
        "layers = [Conv2D(1, 4, 7, stride=3),\n",
        "          ReLU(),\n",
        "          MaxPool2D(),\n",
        "          Conv2D(4, 4, 3, padding=1),\n",
        "          ReLU(),\n",
        "          MaxPool2D(),\n",
        "          Flatten(),\n",
        "          ReLU(),\n",
        "          Linear(16, len(kept_classes)),\n",
        "          Softmax()]\n",
        "\n",
        "# Create the model\n",
        "model = CNN(layers, CrossEntropy(), lr=0.001)\n",
        "\n",
        "# Train the model\n",
        "losses = model.train(x_train, y_train, epochs=30, batch_size=16)\n",
        "\n",
        "# Plot the training loss curve\n",
        "plot_training(losses)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 489
        },
        "id": "eaIADoc8W7CS",
        "outputId": "dfe1026f-eae0-4a26-dbd9-21505d2dea73"
      },
      "execution_count": 9,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "Loss: 0.052: 100%|██████████| 30/30 [13:37<00:00, 27.25s/it]\n"
          ]
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 640x480 with 1 Axes>"
            ],
            "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjkAAAHHCAYAAABdm0mZAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABCmUlEQVR4nO3deXxU9b3/8feZmcxkIRsJZIFAWJRVAoKkEdxKBKxFcbkX6wLSqlWxLmirVAXFq1i16G3dqnW/7raiLYgigv7UIJVdBGQnLEmAkH2fOb8/khkIBAghyZnl9Xw8zmNmvnPO5JPzGM2b7/me79cwTdMUAABAkLFZXQAAAEBbIOQAAICgRMgBAABBiZADAACCEiEHAAAEJUIOAAAISoQcAAAQlAg5AAAgKBFyAABAUCLkAGg31157rdLT01t07AMPPCDDMFq3oGY6mboBWIeQA0CGYTRrW7x4sdWlAkCzGaxdBeD//u//Gr1+/fXXtWDBAr3xxhuN2s8//3wlJSW1+OfU1tbK4/HI5XKd8LF1dXWqq6tTeHh4i39+S1177bVavHixtm3b1u4/G0DLOawuAID1rr766kavlyxZogULFhzRfriKigpFRkY2++eEhYW1qD5Jcjgccjj4XxaA5uNyFYBmOffcczVw4EAtW7ZMZ599tiIjI/XHP/5RkvTRRx/pwgsvVGpqqlwul3r16qWHHnpIbre70WccPrZl27ZtMgxDTzzxhF544QX16tVLLpdLZ5xxhv7zn/80OrapMTmGYeiWW27RnDlzNHDgQLlcLg0YMEDz588/ov7Fixdr2LBhCg8PV69evfS3v/3tpMb5lJeX684771RaWppcLpf69OmjJ554Qod3ji9YsEAjR45UXFycOnTooD59+vjOm9df//pXDRgwQJGRkYqPj9ewYcP01ltvtaguAAfxzyIAzbZ//35dcMEFuuKKK3T11Vf7Ll29+uqr6tChg6ZOnaoOHTroiy++0PTp01VSUqLHH3/8uJ/71ltvqbS0VL/97W9lGIYee+wxXXrppdqyZctxe3++/vpr/fOf/9TNN9+s6Oho/eUvf9Fll12mHTt2KCEhQZK0YsUKjR07VikpKXrwwQfldrs1c+ZMderUqUXnwTRNXXTRRVq0aJF+85vfaPDgwfr000/1+9//Xrt27dKTTz4pSVq7dq1++ctfatCgQZo5c6ZcLpc2bdqkb775xvdZL774om699VZdfvnluu2221RVVaXVq1fru+++05VXXtmi+gA0MAHgMFOmTDEP/9/DOeecY0oyn3/++SP2r6ioOKLtt7/9rRkZGWlWVVX52iZNmmR2797d93rr1q2mJDMhIcEsLCz0tX/00UemJPNf//qXr23GjBlH1CTJdDqd5qZNm3xtq1atMiWZf/3rX31t48aNMyMjI81du3b52jZu3Gg6HI4jPrMph9c9Z84cU5L5P//zP432u/zyy03DMHz1PPnkk6Ykc+/evUf97IsvvtgcMGDAcWsAcOK4XAWg2VwulyZPnnxEe0REhO95aWmp9u3bp7POOksVFRVav379cT93woQJio+P970+66yzJElbtmw57rHZ2dnq1auX7/WgQYMUExPjO9btduvzzz/X+PHjlZqa6tuvd+/euuCCC477+U2ZN2+e7Ha7br311kbtd955p0zT1CeffCJJiouLk1R/Oc/j8TT5WXFxcdq5c+cRl+cAnDxCDoBm69Kli5xO5xHta9eu1SWXXKLY2FjFxMSoU6dOvkHLxcXFx/3cbt26NXrtDTwHDhw44WO9x3uPLSgoUGVlpXr37n3Efk21Ncf27duVmpqq6OjoRu39+vXzvS/Vh7cRI0bouuuuU1JSkq644gq99957jQLP3XffrQ4dOmj48OE65ZRTNGXKlEaXswC0HCEHQLMd2mPjVVRUpHPOOUerVq3SzJkz9a9//UsLFizQn/70J0k6ag/Goex2e5PtZjNmuDiZY9taRESEvvrqK33++ee65pprtHr1ak2YMEHnn3++b1B2v379tGHDBr3zzjsaOXKk/vGPf2jkyJGaMWOGxdUDgY+QA+CkLF68WPv379err76q2267Tb/85S+VnZ3d6PKTlTp37qzw8HBt2rTpiPeaamuO7t27a/fu3SotLW3U7r001717d1+bzWbTqFGjNHv2bP344496+OGH9cUXX2jRokW+faKiojRhwgS98sor2rFjhy688EI9/PDDqqqqalF9AOoRcgCcFG9PyqE9JzU1NXr22WetKqkRu92u7OxszZkzR7t37/a1b9q0yTd25kT94he/kNvt1tNPP92o/cknn5RhGL6xPoWFhUccO3jwYElSdXW1pPo71g7ldDrVv39/maap2traFtUHoB63kAM4KWeeeabi4+M1adIk3XrrrTIMQ2+88YZfXC7yeuCBB/TZZ59pxIgRuummm3wBZeDAgVq5cuUJf964ceN03nnn6d5779W2bduUkZGhzz77TB999JFuv/1230DomTNn6quvvtKFF16o7t27q6CgQM8++6y6du2qkSNHSpJGjx6t5ORkjRgxQklJSVq3bp2efvppXXjhhUeM+QFwYgg5AE5KQkKC/v3vf+vOO+/Ufffdp/j4eF199dUaNWqUxowZY3V5kqShQ4fqk08+0V133aX7779faWlpmjlzptatW9esu78OZ7PZ9PHHH2v69Ol699139corryg9PV2PP/647rzzTt9+F110kbZt26aXX35Z+/btU2Jios455xw9+OCDio2NlST99re/1ZtvvqnZs2errKxMXbt21a233qr77ruv1X5/IFSxdhWAkDV+/HitXbtWGzdutLoUAG2AMTkAQkJlZWWj1xs3btS8efN07rnnWlMQgDZHTw6AkJCSkqJrr71WPXv21Pbt2/Xcc8+purpaK1as0CmnnGJ1eQDaAGNyAISEsWPH6u2331ZeXp5cLpeysrL0yCOPEHCAIEZPDgAACEqMyQEAAEGJkAMAAIJSyI3J8Xg82r17t6Kjo2UYhtXlAACAZjBNU6WlpUpNTZXN1rw+mpALObt371ZaWprVZQAAgBbIzc1V165dm7VvyIUc7zTpubm5iomJsbgaAADQHCUlJUpLSzuh5U5CLuR4L1HFxMQQcgAACDAnMtSEgccAACAoEXIAAEBQIuQAAICgRMgBAABBiZADAACCEiEHAAAEJUIOAAAISoQcAAAQlAg5AAAgKBFyAABAUCLkAACAoETIAQAAQYmQ04pyCyv0U36p1WUAAAARclrNJ2v2aNSfv9Td/1gt0zStLgcAgJBHyGklQ7vHy24ztGJHkeatybO6HAAAQh4hp5V0jgnXDWf3lCT9af561dR5LK4IAIDQRshpRTec3VOdol3aUVihN5Zst7ocAABCGiGnFUW5HJp6/qmSpL9+sVHFFbUWVwQAQOgi5LSy/xraVacmdVBRRa2eWbzJ6nIAAAhZhJxW5rDbNO2CfpKkV7/ZptzCCosrAgAgNBFy2sC5fTppRO8E1bg9evzTDVaXAwBASCLktAHDMDTtgn4yDOnjVbu1KrfI6pIAAAg5hJw2MrBLrC4Z0kWS9PC8dUwQCABAOyPktKG7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sgIABDFLQ867776rqVOnasaMGVq+fLkyMjI0ZswYFRQUNLl/TU2Nzj//fG3btk0ffPCBNmzYoBdffFFdunRp58oDWz/vpIDcRg4ACGIOK3/47Nmzdf3112vy5MmSpOeff15z587Vyy+/rHvuueeI/V9++WUVFhbq22+/VVhYmCQpPT29PUsOCn25jRwAEAIs68mpqanRsmXLlJ2dfbAYm03Z2dnKyclp8piPP/5YWVlZmjJlipKSkjRw4EA98sgjcrsZQHsiDr2NnOUdAADByrKenH379sntdispKalRe1JSktavX9/kMVu2bNEXX3yhq666SvPmzdOmTZt08803q7a2VjNmzGjymOrqalVXV/tel5TQe3FKUgfZbYaKK2uVV1KllNgIq0sCAKDVWT7w+ER4PB517txZL7zwgoYOHaoJEybo3nvv1fPPP3/UY2bNmqXY2FjflpaW1o4V+yeXw65enaIkcckKABC8WhRycnNztXPnTt/rpUuX6vbbb9cLL7zQ7M9ITEyU3W5Xfn5+o/b8/HwlJyc3eUxKSopOPfVU2e12X1u/fv2Ul5enmpqaJo+ZNm2aiouLfVtubm6zawxmfRl8DAAIci0KOVdeeaUWLVokScrLy9P555+vpUuX6t5779XMmTOb9RlOp1NDhw7VwoULfW0ej0cLFy5UVlZWk8eMGDFCmzZtksfj8bX99NNPSklJkdPpbPIYl8ulmJiYRhsOznzM8g4AgGDVopDzww8/aPjw4ZKk9957TwMHDtS3336rN998U6+++mqzP2fq1Kl68cUX9dprr2ndunW66aabVF5e7rvbauLEiZo2bZpv/5tuukmFhYW67bbb9NNPP2nu3Ll65JFHNGXKlJb8GiHNO1cOl6sAAMGqRQOPa2tr5XK5JEmff/65LrroIklS3759tWfPnmZ/zoQJE7R3715Nnz5deXl5Gjx4sObPn+8bjLxjxw7ZbAdzWFpamj799FPdcccdGjRokLp06aLbbrtNd999d0t+jZDmnStny94yVdW6FR5mP84RAAAEFsNswT3EmZmZOu+883ThhRdq9OjRWrJkiTIyMrRkyRJdfvnljcbr+JuSkhLFxsaquLg4pC9dmaapIQ8tUFFFrf79u5Ea2CXW6pIAADiqlvz9btHlqj/96U/629/+pnPPPVe/+tWvlJGRIal+HhvvZSz4N8Mw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unSp1SX5tQceeECGYTTa+vbta3VZfuerr77SuHHjlJqaKsMwNGfOnEbvm6ap6dOnKyUlRREREcrOztbGjRutKdaPHO+8XXvttUd8/8aOHWtNsX5i1qxZOuOMMxQdHa3OnTtr/Pjx2rBhQ6N9qqqqNGXKFCUkJKhDhw667LLLlJ+fb1HF/qE55+3cc8894vt24403WlSxf3juuec0aNAg36R/WVlZ+uSTT3zvt9Z3jZDTCt59911NnTpVM2bM0PLly5WRkaExY8aooKDA6tL82oABA7Rnzx7f9vXXX1tdkt8pLy9XRkaGnnnmmSbff+yxx/SXv/xFzz//vL777jtFRUVpzJgxqqqqaudK/cvxzpskjR07ttH37+23327HCv3Pl19+qSlTpmjJkiVasGCBamtrNXr0aJWXl/v2ueOOO/Svf/1L77//vr788kvt3r1bl156qYVVW685502Srr/++kbft8cee8yiiv1D165d9eijj2rZsmX6/vvv9fOf/1wXX3yx1q5dK6kVv2smTtrw4cPNKVOm+F673W4zNTXVnDVrloVV+bcZM2aYGRkZVpcRUCSZH374oe+1x+Mxk5OTzccff9zXVlRUZLpcLvPtt9+2oEL/dPh5M03TnDRpknnxxRdbUk+gKCgoMCWZX375pWma9d+tsLAw8/333/fts27dOlOSmZOTY1WZfufw82aapnnOOeeYt912m3VFBYj4+Hjz73//e6t+1+jJOUk1NTVatmyZsrOzfW02m03Z2dnKycmxsDL/t3HjRqWmpqpnz5666qqrtGPHDqtLCihbt25VXl5eo+9ebGysMjMz+e41w+LFi9W5c2f16dNHN910k/bv3291SX6luLhYktSxY0dJ0rJly1RbW9vo+9a3b19169aN79shDj9vXm+++aYSExM1cOBATZs2TRUVFVaU55fcbrfeeecdlZeXKysrq1W/ayG3QGdr27dvn9xut5KSkhq1JyUlaf369RZV5f8yMzP16quvqk+fPtqzZ48efPBBnXXWWfrhhx8UHR1tdXkBIS8vT5Ka/O5530PTxo4dq0svvVQ9evTQ5s2b9cc//lEXXHCBcnJyZLfbrS7Pch6PR7fffrtGjBihgQMHSqr/vjmdTsXFxTXal+/bQU2dN0m68sor1b17d6Wmpmr16tW6++67tWHDBv3zn/+0sFrrrVmzRllZWaqqqlKHDh304Ycfqn///lq5cmWrfdcIObDEBRdc4Hs+aNAgZWZmqnv37nrvvff0m9/8xsLKEAquuOIK3/PTTjtNgwYNUq9evbR48WKNGjXKwsr8w5QpU/TDDz8wTu4EHe283XDDDb7np512mlJSUjRq1Cht3rxZvXr1au8y/UafPn20cuVKFRcX64MPPtCkSZP05ZdfturP4HLVSUpMTJTdbj9i1Hd+fr6Sk5MtqirwxMXF6dRTT9WmTZusLiVgeL9ffPdOXs+ePZWYmMj3T9Itt9yif//731q0aJG6du3qa09OTlZNTY2Kiooa7c/3rd7RzltTMjMzJSnkv29Op1O9e/fW0KFDNWvWLGVkZOh///d/W/W7Rsg5SU6nU0OHDtXChQt9bR6PRwsXLlRWVpaFlQWWsrIybd68WSkpKVaXEjB69Oih5OTkRt+9kpISfffdd3z3TtDOnTu1f//+kP7+maapW265RR9++KG++OIL9ejRo9H7Q4cOVVhYWKPv24YNG7Rjx46Q/r4d77w1ZeXKlZIU0t+3png8HlVXV7fud611x0aHpnfeecd0uVzmq6++av7444/mDTfcYMbFxZl5eXlWl+a37rzzTnPx4sXm1q1bzW+++cbMzs42ExMTzYKCAqtL8yulpaXmihUrzBUrVpiSzNmzZ5srVqwwt2/fbpqmaT766KNmXFyc+dFHH5mrV682L774YrNHjx5mZWWlxZVb61jnrbS01LzrrrvMnJwcc+vWrebnn39unn766eYpp5xiVlVVWV26ZW666SYzNjbWXLx4sblnzx7fVlFR4dvnxhtvNLt162Z+8cUX5vfff29mZWWZWVlZFlZtveOdt02bNpkzZ840v//+e3Pr1q3mRx99ZPbs2dM8++yzLa7cWvfcc4/55Zdfmlu3bjVXr15t3nPPPaZhGOZnn31mmmbrfdcIOa3kr3/9q9mtWzfT6XSaw4cPN5csWWJ1SX5twoQJZkpKiul0Os0uXbqYEyZMMDdt2mR1WX5n0aJFpqQjtkmTJpmmWX8b+f33328mJSWZLpfLHDVqlLlhwwZri/YDxzpvFRUV5ujRo81OnTqZYWFhZvfu3c3rr78+5P9R0tT5kmS+8sorvn0qKyvNm2++2YyPjzcjIyPNSy65xNyzZ491RfuB4523HTt2mGeffbbZsWNH0+Vymb179zZ///vfm8XFxdYWbrFf//rXZvfu3U2n02l26tTJHDVqlC/gmGbrfdcM0zTNFvYsAQAA+C3G5AAAgKBEyAEAAEGJkAMAAIISIQcAAAQlQg4AAAhKhBwAABCUCDkAACAoEXIAhDzDMDRnzhyrywDQygg5ACx17bXXyjCMI7axY8daXRqAAOewugAAGDt2rF555ZVGbS6Xy6JqAAQLenIAWM7lcik5ObnRFh8fL6n+UtJzzz2nCy64QBEREerZs6c++OCDRsevWbNGP//5zxUREaGEhATdcMMNKisra7TPyy+/rAEDBsjlciklJUW33HJLo/f37dunSy65RJGRkTrllFP08ccft+0vDaDNEXIA+L37779fl112mVatWqWrrrpKV1xxhdatWydJKi8v15gxYxQfH6///Oc/ev/99/X55583CjHPPfecpkyZohtuuEFr1qzRxx9/rN69ezf6GQ8++KD++7//W6tXr9YvfvELXXXVVSosLGzX3xNAK2u9NUUB4MRNmjTJtNvtZlRUVKPt4YcfNk2zfpXnG2+8sdExmZmZ5k033WSapmm+8MILZnx8vFlWVuZ7f+7cuabNZvOtLJ6ammree++9R61Bknnffff5XpeVlZmSzE8++aTVfk8A7Y8xOQAsd9555+m5555r1NaxY0ff86ysrEbvZWVlaeXKlZKkdevWKSMjQ1FRUb73R4wYIY/How0bNsgwDO3evVujRo06Zg2DBg3yPY+KilJMTIwKCgpa+isB8AOEHACWi4qKOuLyUWuJiIho1n5hYWGNXhuGIY/H0xYlAWgnjMkB4PeWLFlyxOt+/fpJkvr166dVq1apvLzc9/4333wjm82mPn36KDo6Wunp6Vq4cGG71gzAevTkALBcdXW18vLyGrU5HA4lJiZKkt5//30NGzZMI0eO1JtvvqmlS5fqpZdekiRdddVVmjFjhiZNmqQHHnhAe/fu1e9+9ztdc801SkpKkiQ98MADuvHGG9W5c2ddcMEFKi0t1TfffKPf/e537fuLAmhXhBwAlps/f75SUlIatfXp00fr16+XVH/n0zvvvKObb75ZKSkpevvtt9W/f39JUmRkpD799FPddtttOuOMMxQZGanLLrtMs2fP9n3WpEmTVFVVpSeffFJ33XWXEhMTdfnll7ffLwjAEoZpmqbVRQDA0RiGoQ8//FDjx4+3uhQAAYYxOQAAICgRcgAAQFBiTA4Av8YVdQAtRU8OAAAISoQcAAAQlAg5AAAgKBFyAABAUCLkAACAoETIAQAAQYmQAwAAghIhBwAABCVCDgAACEr/H65NVQlLeou4AAAAAElFTkSuQmCC\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Evaluation"
      ],
      "metadata": {
        "id": "H4Uh-ddyk2mR"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "We can measure the models accuracy on the test dataset."
      ],
      "metadata": {
        "id": "sgO7eZ9Uk5X0"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Test the model\n",
        "y_prediction = np.argmax(model(x_test), axis=1)\n",
        "acc = 100 * np.mean(y_prediction == y_test)\n",
        "print(f'Test accuracy with {len(y_train)} training examples on {len(y_test)} test samples is {acc:.2f}%')"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "I3M4fWNaZoHW",
        "outputId": "b40e8d17-fc2b-49b8-8c98-09dde3d1d706"
      },
      "execution_count": 10,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Test accuracy with 11000 training examples on 10000 test samples is 98.63%\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "The confusion matrix can also be observed:"
      ],
      "metadata": {
        "id": "I2DrYQPilBlF"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Plot the confusion matrix\n",
        "plot_confusion_matrix(y_test, y_prediction, class_names, kept_classes)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 518
        },
        "id": "49LwgNLMj72R",
        "outputId": "8e90a5b1-1e81-4441-d265-2d4c682f69c4"
      },
      "execution_count": 11,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 640x480 with 2 Axes>"
            ],
            "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjQAAAH1CAYAAADh12SPAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABocklEQVR4nO3dd1gUZ9cG8HuWKh2VqgiKXaKixobGhr339gpi12DD2BtqrLEmUbFj7Im9oogSaywottgrFlBRiihtd74/+Ni4ggrsyjLL/buuud7s7DPPnIVXOJynjCCKoggiIiIiCZNpOwAiIiIidTGhISIiIsljQkNERESSx4SGiIiIJI8JDREREUkeExoiIiKSPCY0REREJHlMaIiIiEjymNAQERGR5DGhISIAwN27d9GkSRNYWlpCEATs3r1bo/0/evQIgiAgMDBQo/1KWf369VG/fn1th0GkE5jQEOUh9+/fx8CBA1GiRAkYGxvDwsICHh4eWLJkCT58+PBN7+3t7Y1r165h5syZ2LBhA6pVq/ZN75ebevfuDUEQYGFhkenX8e7duxAEAYIgYP78+dnu//nz5/D390d4eLgGoiWinNDXdgBElObAgQPo3LkzjIyM4OXlBTc3NyQnJ+PUqVMYPXo0bty4gZUrV36Te3/48AFnz57FxIkT4evr+03u4ezsjA8fPsDAwOCb9P81+vr6eP/+Pfbt24cuXbqovLdp0yYYGxsjMTExR30/f/4c06ZNg4uLCypXrpzl644cOZKj+xFRRkxoiPKAhw8folu3bnB2dsaxY8fg4OCgfO/HH3/EvXv3cODAgW92/1evXgEArKysvtk9BEGAsbHxN+v/a4yMjODh4YEtW7ZkSGg2b96Mli1bYseOHbkSy/v372FiYgJDQ8NcuR9RfsAhJ6I8YN68eXj37h3WrFmjksykK1myJIYPH658nZqaihkzZsDV1RVGRkZwcXHBhAkTkJSUpHKdi4sLWrVqhVOnTqF69eowNjZGiRIl8Mcffyjb+Pv7w9nZGQAwevRoCIIAFxcXAGlDNen//TF/f38IgqByLjg4GHXq1IGVlRXMzMxQpkwZTJgwQfn+5+bQHDt2DHXr1oWpqSmsrKzQtm1b3Lx5M9P73bt3D71794aVlRUsLS3h4+OD9+/ff/4L+4kePXrg0KFDiImJUZ67cOEC7t69ix49emRo/+bNG/z000/47rvvYGZmBgsLCzRv3hxXrlxRtgkNDcX3338PAPDx8VEOXaV/zvr168PNzQ1hYWH44YcfYGJiovy6fDqHxtvbG8bGxhk+f9OmTWFtbY3nz59n+bMS5TdMaIjygH379qFEiRKoXbt2ltr369cPU6ZMQZUqVbBo0SLUq1cPs2fPRrdu3TK0vXfvHjp16oTGjRtjwYIFsLa2Ru/evXHjxg0AQIcOHbBo0SIAQPfu3bFhwwYsXrw4W/HfuHEDrVq1QlJSEqZPn44FCxagTZs2OH369BevO3r0KJo2bYqXL1/C398ffn5+OHPmDDw8PPDo0aMM7bt06YL4+HjMnj0bXbp0QWBgIKZNm5blODt06ABBELBz507luc2bN6Ns2bKoUqVKhvYPHjzA7t270apVKyxcuBCjR4/GtWvXUK9ePWVyUa5cOUyfPh0AMGDAAGzYsAEbNmzADz/8oOwnOjoazZs3R+XKlbF48WI0aNAg0/iWLFkCGxsbeHt7Qy6XAwBWrFiBI0eO4LfffoOjo2OWPytRviMSkVbFxsaKAMS2bdtmqX14eLgIQOzXr5/K+Z9++kkEIB47dkx5ztnZWQQgnjhxQnnu5cuXopGRkThq1CjluYcPH4oAxF9++UWlT29vb9HZ2TlDDFOnThU//vGxaNEiEYD46tWrz8adfo9169Ypz1WuXFm0tbUVo6OjleeuXLkiymQy0cvLK8P9+vTpo9Jn+/btxUKFCn32nh9/DlNTU1EURbFTp05io0aNRFEURblcLtrb24vTpk3L9GuQmJgoyuXyDJ/DyMhInD59uvLchQsXMny2dPXq1RMBiAEBAZm+V69ePZVzhw8fFgGIP//8s/jgwQPRzMxMbNeu3Vc/I1F+xwoNkZbFxcUBAMzNzbPU/uDBgwAAPz8/lfOjRo0CgAxzbcqXL4+6desqX9vY2KBMmTJ48OBBjmP+VPrcmz179kChUGTpmhcvXiA8PBy9e/dGwYIFlecrVqyIxo0bKz/nxwYNGqTyum7duoiOjlZ+DbOiR48eCA0NRWRkJI4dO4bIyMhMh5uAtHk3Mlnaj0m5XI7o6GjlcNqlS5eyfE8jIyP4+PhkqW2TJk0wcOBATJ8+HR06dICxsTFWrFiR5XsR5VdMaIi0zMLCAgAQHx+fpfaPHz+GTCZDyZIlVc7b29vDysoKjx8/VjlfrFixDH1YW1vj7du3OYw4o65du8LDwwP9+vWDnZ0dunXrhj///POLyU16nGXKlMnwXrly5fD69WskJCSonP/0s1hbWwNAtj5LixYtYG5ujm3btmHTpk34/vvvM3wt0ykUCixatAilSpWCkZERChcuDBsbG1y9ehWxsbFZvmeRIkWyNQF4/vz5KFiwIMLDw/Hrr7/C1tY2y9cS5VdMaIi0zMLCAo6Ojrh+/Xq2rvt0Uu7n6OnpZXpeFMUc3yN9fke6AgUK4MSJEzh69Ch69eqFq1evomvXrmjcuHGGtupQ57OkMzIyQocOHbB+/Xrs2rXrs9UZAJg1axb8/Pzwww8/YOPGjTh8+DCCg4NRoUKFLFeigLSvT3ZcvnwZL1++BABcu3YtW9cS5VdMaIjygFatWuH+/fs4e/bsV9s6OztDoVDg7t27KuejoqIQExOjXLGkCdbW1iorgtJ9WgUCAJlMhkaNGmHhwoX4999/MXPmTBw7dgzHjx/PtO/0OG/fvp3hvVu3bqFw4cIwNTVV7wN8Ro8ePXD58mXEx8dnOpE63fbt29GgQQOsWbMG3bp1Q5MmTeDp6Znha5LV5DIrEhIS4OPjg/Lly2PAgAGYN28eLly4oLH+iXQVExqiPGDMmDEwNTVFv379EBUVleH9+/fvY8mSJQDShkwAZFiJtHDhQgBAy5YtNRaXq6srYmNjcfXqVeW5Fy9eYNeuXSrt3rx5k+Ha9A3mPl1Kns7BwQGVK1fG+vXrVRKE69ev48iRI8rP+S00aNAAM2bMwO+//w57e/vPttPT08tQ/fnrr7/w7NkzlXPpiVdmyV92jR07Fk+ePMH69euxcOFCuLi4wNvb+7NfRyJKw431iPIAV1dXbN68GV27dkW5cuVUdgo+c+YM/vrrL/Tu3RsAUKlSJXh7e2PlypWIiYlBvXr1cP78eaxfvx7t2rX77JLgnOjWrRvGjh2L9u3bY9iwYXj//j2WL1+O0qVLq0yKnT59Ok6cOIGWLVvC2dkZL1++xLJly1C0aFHUqVPns/3/8ssvaN68OWrVqoW+ffviw4cP+O2332BpaQl/f3+NfY5PyWQyTJo06avtWrVqhenTp8PHxwe1a9fGtWvXsGnTJpQoUUKlnaurK6ysrBAQEABzc3OYmpqiRo0aKF68eLbiOnbsGJYtW4apU6cql5GvW7cO9evXx+TJkzFv3rxs9UeUr2h5lRURfeTOnTti//79RRcXF9HQ0FA0NzcXPTw8xN9++01MTExUtktJSRGnTZsmFi9eXDQwMBCdnJzE8ePHq7QRxbRl2y1btsxwn0+XC39u2bYoiuKRI0dENzc30dDQUCxTpoy4cePGDMu2Q0JCxLZt24qOjo6ioaGh6OjoKHbv3l28c+dOhnt8urT56NGjooeHh1igQAHRwsJCbN26tfjvv/+qtEm/36fLwtetWycCEB8+fPjZr6koqi7b/pzPLdseNWqU6ODgIBYoUED08PAQz549m+ly6z179ojly5cX9fX1VT5nvXr1xAoVKmR6z4/7iYuLE52dncUqVaqIKSkpKu1GjhwpymQy8ezZs1/8DET5mSCK2ZhNR0RERJQHcQ4NERERSR4TGiIiIpI8JjREREQkeUxoiIiISPKY0BAREZHkMaEhIiIiyePGehKkUCjw/PlzmJuba3TLdSIiyh2iKCI+Ph6Ojo7KJ7prWmJiIpKTkzXSl6GhIYyNjTXS17fChEaCnj9/DicnJ22HQUREaoqIiEDRokU13m9iYiKKO5sh8qVmHg5rb2+Phw8f5umkhgmNBJmbmwMA7oYVgbkZRw11XfcK1bUdAuUiUYNPJ6e8K1VMwSkcUP4817Tk5GREvpTjcZgLLMzV+z0RF6+Ac9VHSE5OZkJDmpU+zGRuJlP7/6iU9+kLBtoOgXKRKPDfdL4havZJ7ZkxMxdgZq7ePRSQxtQGJjREREQ6Si4qIFfzAUdyUaGZYL4x/ilAREREkscKDRERkY5SQIQC6pVo1L0+tzChISIi0lEKKKDugJH6PeQODjkRERGR5LFCQ0REpKPkogi5qN6QkbrX5xYmNERERDqKc2iIiIhI8hQQIc8nCQ3n0BAREZHksUJDRESkozjkRERERJKXnyYFc8iJiIiIJI8VGiIiIh2l+P9D3T6kgAkNERGRjpJrYJWTutfnFg45ERERkeSxQkNERKSj5GLaoW4fUsCEhoiISEdxDg0RERFJngIC5BDU7kMKOIeGiIiIJI8VGiIiIh2lENMOdfuQAiY0REREOkqugSEnda/PLRxyIiIiIsljhYaIiEhH5acKDRMaIiIiHaUQBShENVc5qXl9buGQExEREUkeKzREREQ6ikNOREREJHlyyCBXczBGrqFYvjUOOREREZHksUJDRESko0QNTAoWJTIpmAkNERGRjuIcGiIiIpI8uSiDXFRzDo1EHn3AOTREREQkeazQEBER6SgFBCjUrF0oII0SDRMaIiIiHZWf5tBwyImIiIgkjxUaIiIiHaWZScEcciIiIiItSptDo+bDKTnkRERERJQ7WKEhIiLSUQoNPMuJq5yIiIhIq/LTHBoOOREREZHksUJDRESkoxSQcWM9IiIikja5KECu5tOy1b0+tzChISIi0lFyDUwKlkukQsM5NERERCR5rNAQERHpKIUog0LNVU4KiaxyYkJDRESkozjkRERERCQhrNAQERHpKAXUX6Wk0Ewo3xwTGiIiIh2lmX1opDGYI40oiYiIiL6AFRoiIiIdpZlnOUmj9sGEhoiISEcpIEABdefQSGOnYGmkXVng7++PypUrf7FN/fr1MWLEiFyJhzJauT4e1Rs9h13pJ7Ar/QT1W7/A4WMfAABv3srhN/ENKtV5hoIlnqB0tacYNekNYuMyn44W/UaOklWfwsTxMWJi/2tz5lwiGraJRNEKEShY4gkq132G31bG5crnI/V0Hd0GR5K3YNB8L+W54Uv7IvDmYuyLXY8/n62A/45RcCrjqMUoKae+q1sO0/eMxdaIAATL/0Tttt+rvB8s/zPTo/Oo1lqKmKRGaxUaQfhyxjd16lT4+/tr9J47d+6EgYHBF9s8evQIxYsXx+XLlzNNkKZNm4a7d+9i48aNEAQBu3btQrt27TQap64q4qCH6ROsUbK4PkQR2PjXO3TxeYmzRxwgisCLKDlmTbFGudIGePI0FcPGvcGLKDk2r7LJ0NfgUdFwK2eA5y/kKudNTGQY5GMOt/IGMDWR4cz5RAwd8wYmJgL6/s88tz4qZVPpqiXQsl8j3L/6WOX83UsPcWzLabyMeA1zazP0mtIJsw+Mh1fpYVAopLE3BqUxNjXCgyuPcHjdMfjvGJ3h/S6O/VVeV2/uDr9Vg3By57ncClEnccgpF7x48UL539u2bcOUKVNw+/Zt5TkzMzON37NgwYJffD85OfmrfezZswfjxo3TVEj5SssmJiqvp42zxuo/3uF8WBJ69zDHltX/JS4lXAzgP9YKfYa+RmqqCH39/xLglevjERunwPiRljhyLFGlz8rfGaLyd4bK185OZthz8D3OnEtiQpNHGZsaYdwfvlg0eBV6jG+v8t7BNceU/x31+DUCp/6JFWFzYedigxcPXuZ2qKSGC0HhuBAU/tn330bFqryu1eZ7XDl+A5EP+X1Wh2Y21pNGQqO1KO3t7ZWHpaUlBEFQOZdZQhMaGorq1avD1NQUVlZW8PDwwOPHqn/RbdiwAS4uLrC0tES3bt0QHx+vfO/TIScXFxfMmDEDXl5esLCwwIABA1C8eHEAgLu7OwRBQP369ZXtIyIicOPGDTRr1gwuLi4AgPbt20MQBOVrAFi+fDlcXV1haGiIMmXKYMOGDSoxCoKA5cuXo3nz5ihQoABKlCiB7du35/ArKU1yuYi/dicg4b0CNaoZZdomNk4BCzOZSjJz804yZi+KxeolhSGTfX1cN/xaMv65mIQ6NTO/B2nf0F/74PzBy7h87PoX2xmbGKGpVz28eBCFVxHRuRQdaYOVrSVqtHDHoXXHvt6YvkghCho5pEAaaReA1NRUtGvXDvXq1cPVq1dx9uxZDBgwQGXo6v79+9i9ezf279+P/fv34++//8acOXO+2O/8+fNRqVIlXL58GZMnT8b58+cBAEePHsWLFy+wc+dOZdu9e/eifv36sLCwwIULFwAA69atw4sXL5Svd+3aheHDh2PUqFG4fv06Bg4cCB8fHxw/flzlvpMnT0bHjh1x5coV9OzZE926dcPNmzc18rXKy67fTIZNySewcnmCYeOisXWNLcqVNszQ7nW0HHMWx8Lnf/8ltklJInoPeY1Zk63gVPTLxcWSVZ/CyuUx6jR/gYG9zeHTk9WZvKh+l1oo6e6CNZO2frZN64GNsefNOuyNCcT3zSphXItZSE2Rf7Y9SV8Tr3p4H5+IUzvPazsUkhDJrHKKi4tDbGwsWrVqBVdXVwBAuXLlVNooFAoEBgbC3Dztl1evXr0QEhKCmTNnfrbfhg0bYtSoUcrXenp6AIBChQrB3t5epe2ePXvQtm1bAICNTdrwiJWVlUq7+fPno3fv3hgyZAgAwM/PD//88w/mz5+PBg0aKNt17twZ/fr1AwDMmDEDwcHB+O2337Bs2bIMMSYlJSEpKUnlayFVpV0N8E+wA2LjFdi9/z0GDH+NwzvtVJKauHgFOni9RNnSBpg0ykp5fsrstyhT0gDdO359OPLoLju8SxBx/lISpsyKgauLAbq0N/0WH4lyyKZoQQxe4I1xLWYhJSnls+1CtpxCWMg1FLK3Qie/Vpi0eThG1PP/4jUkbU19GuDY5pP8HmuAQgNDTtxYTw1PnjyBmZmZ8pg1axYKFiyI3r17o2nTpmjdujWWLFmiMg8HSBtCSk9mAMDBwQEvX355/LVatWpZiikuLg5///032rRp88V2N2/ehIeHh8o5Dw+PDNWXWrVqZXj9uQrN7NmzYWlpqTycnJyyFHNeZGgowLW4AapUNML0Cdb4rrwhlq7+b1gw/p0CbXu8hLmpDNvW2MLA4L8KXOipROzc/x7mTo9h7vQYLbpEAQCc3CIw45cYlfu4FDOAWzlD9OlpDt/+5pi5QPV90r5SVUrA2s4Sy87NwqH3G3Ho/UZUqlce7Xyb4tD7jcohxfdxH/D8XiSunbqFGV0XwamMIzzaff+V3kmq3OqURbGyRXBoDYebNCH9advqHlKQJys0jo6OCA8PV75On8y7bt06DBs2DEFBQdi2bRsmTZqE4OBg1KxZEwAyrGASBAEKxZefQmFqmrW/2g8dOoTy5ctrJZkYP348/Pz8lK/j4uIkndR8TCGKSE5OW60SF69Amx5RMDIU8FegDYyNVcdtt6y2wYfE/1a2hIUnY5BfNI7uskdxl8//X1mhAJKSuSImr7l87DoGuKuudhm1ahAibj/Hn/P3ZrqKSRAEQBBgYJgnf3SRBjTv0xB3Lt7Hg09WvBF9TZ78qaCvr4+SJUtm+p67uzvc3d0xfvx41KpVC5s3b1YmNJpgaJg29CGXq47RfzzclM7AwCBDu3LlyuH06dPw9vZWnjt9+jTKly+v0u6ff/6Bl5eXymt3d/dMYzIyMoKRkfQntU6Z9RZNGhaAUxF9xL9T4M9dCThxJgl7N1siLl6B1t2j8OGDiLW/FUbcOxFx79K+tjaFZNDTE1DCRTVhjX6TlqyWKWUAK8u0vyAC1sXDqYgeypRMa3vqn0QsCYjD4L6cQ5PXfHiXiEc3nqqcS0xIQlz0Ozy68RT2xW1Rv3MthAVfRczrONgUKYiuY9oi+UPyF1fLUN5kbGqEIiX/G563d7GFayVnxL15p5zkbWJeAHU71cTK0Rs+1w1lkxwC5GpujKfu9bklTyY0mXn48CFWrlyJNm3awNHREbdv38bdu3dVkgJNsLW1RYECBRAUFISiRYvC2NgYpqamOHToEH766SeVti4uLggJCYGHhweMjIxgbW2N0aNHo0uXLnB3d4enpyf27duHnTt34ujRoyrX/vXXX6hWrRrq1KmDTZs24fz581izZo1GP0te8/K1HP2GvUbkSzkszWVwK2eIvZtt0aheAZw4k4gLl9KWzbvVfq5y3c1zReDslLX/q4oKEVNnx+DRk1To6wPFnQ0wY6I1+vXS/DYA9G0lJ6bAzaMM2g9tDjNrU8RExeLaqZsYUW8qYl5Jdx5ZflW6misWHPNXvh68MO2PviPrQ/FLn7S5g/W71YYgCDi25ZQ2QtRJmhgy4pCThpmYmODWrVtYv349oqOj4eDggB9//BEDBw7U6H309fXx66+/Yvr06ZgyZQrq1q2LyZMnw8zMDFWqVFFpu2DBAvj5+WHVqlUoUqQIHj16hHbt2mHJkiWYP38+hg8fjuLFi2PdunUqy7+BtA36tm7diiFDhsDBwQFbtmzJUMXRNQELC3/2vR9qG+P9c+ds9ZfZNYP7WmBwX4scxUfaN7rxDOV/v3nxFpPaztNiNKRJV//+F431unyxzcFVITi4KiSXIiJdI4iiyMkFXzFs2DCkpqZmugIpJ9TdYTguLg6WlpaIvO0EC3NpZM6Uc22da329EekMUc4l6flBqpiCUHE3YmNjYWGh+T/C0n9PTDnnCWOzL++Q/zWJ71IwvcbRbxarpkimQqNNbm5uGVYlERER5XX5achJGlFq2YABA/Ddd99pOwwiIiJJWLp0KVxcXGBsbIwaNWooN639nMWLF6NMmTIoUKAAnJycMHLkSCQmJn7xmk+xQqMFHOUjIqLcoI2HU27btg1+fn4ICAhAjRo1sHjxYjRt2hS3b9+Gra1thvabN2/GuHHjsHbtWtSuXRt37txB7969IQgCFi5cmOX7skJDRESko0QIUKh5iNlctr1w4UL0798fPj4+KF++PAICAmBiYoK1a9dm2v7MmTPw8PBAjx494OLigiZNmqB79+5frep8igkNERGRjkqv0Kh7AGkTjT8+Pn4kT7rk5GSEhYXB09NTeU4mk8HT0xNnz57NNMbatWsjLCxMmcA8ePAABw8eRIsWLbL1WZnQEBER0Vc5OTmpPIZn9uzZGdq8fv0acrkcdnZ2Kuft7OwQGRmZab89evTA9OnTUadOHRgYGMDV1RX169fHhAkTshUf59AQERHpKIUoQCGqt9Nv+vUREREqy7Y1tYN9aGgoZs2ahWXLlqFGjRq4d+8ehg8fjhkzZmDy5MlZ7ocJDRERkY6Sa+Bp2+nXW1hYfHUfmsKFC0NPTw9RUVEq56OiomBvb5/pNZMnT0avXr3Qr18/AMB3332HhIQEDBgwABMnToRMlrX4OeREREREGmFoaIiqVasiJOS/HZ8VCgVCQkI+u5/b+/fvMyQtenp6ALK3KpgVGiIiIh2lySGnrPLz84O3tzeqVauG6tWrY/HixUhISICPjw8AwMvLC0WKFFHOwWndujUWLlwId3d35ZDT5MmT0bp1a2VikxVMaIiIiHSUAjIo1ByMye71Xbt2xatXrzBlyhRERkaicuXKCAoKUk4UfvLkiUpFZtKkSRAEAZMmTcKzZ89gY2OD1q1bY+bMmdm6L5/lJEF8llP+wmc55S98llP+kFvPcvI91R5Gaj7LKeldCn6vs4vPciIiIiLtkIsC5GoOOal7fW5hQkNERKSjtDGHRls4XkFERESSxwoNERGRjhJFGRRqPpxSVPP63MKEhoiISEfJIUCezYdLZtaHFDChISIi0lEKUf05MAqJrIWWRh2JiIiI6AtYoSEiItJRCg3MoVH3+tzChIaIiEhHKSBAoeYcGHWvzy3SSLuIiIiIvoAVGiIiIh3FnYKJiIhI8vLTHBppRElERET0BazQEBER6SgFNPAsJ4lMCmZCQ0REpKNEDaxyEiWS0HDIiYiIiCSPFRoiIiIdpRA1MOTEVU5ERESkTflplRMTGiIiIh2Vnyo00ki7iIiIiL6AFRoiIiIdlZ+e5cSEhoiISEdxyImIiIhIQlihISIi0lH5qULDhIaIiEhH5aeEhkNOREREJHms0BAREemo/FShYUJDRESko0Sov+xa1Ewo3xyHnIiIiEjyWKEhIiLSURxyIiIiIsljQkNERESSl58SGs6hISIiIsljhYaIiEhH5acKDRMaIiIiHSWKAkQ1ExJ1r88tHHIiIiIiyWOFhoiISEcpIKi9sZ661+cWJjREREQ6Kj/NoeGQExEREUkeKzREREQ6Kj9NCmZCQ0REpKM45EREREQkIazQEBER6SgOOZEkdK9YC/qCgbbDoG9s56OT2g6BclH7otW1HQLlBlHMpduoP+TEhIaIiIi0SoT6uVPupF7q4xwaIiIikjxWaIiIiHSUAgIE7hRMREREUpafJgVzyImIiIgkjxUaIiIiHaUQBQj5ZGM9JjREREQ6ShQ1sMpJIsucOOREREREkscKDRERkY7KT5OCmdAQERHpqPyU0HDIiYiIiCSPFRoiIiIdxVVOREREJHn5aZUTExoiIiIdlZbQqDuHRkPBfGOcQ0NERESSxwoNERGRjspPq5yY0BAREeko8f8PdfuQAg45ERERkeSxQkNERKSj8tOQEys0REREukrU0JFNS5cuhYuLC4yNjVGjRg2cP3/+i+1jYmLw448/wsHBAUZGRihdujQOHjyYrXuyQkNEREQas23bNvj5+SEgIAA1atTA4sWL0bRpU9y+fRu2trYZ2icnJ6Nx48awtbXF9u3bUaRIETx+/BhWVlbZui8TGiIiIl2lgSEnZPP6hQsXon///vDx8QEABAQE4MCBA1i7di3GjRuXof3atWvx5s0bnDlzBgYGBgAAFxeXbIfJISciIiIdlb5TsLpHViUnJyMsLAyenp7KczKZDJ6enjh79mym1+zduxe1atXCjz/+CDs7O7i5uWHWrFmQy+XZ+qys0BAREdFXxcXFqbw2MjKCkZGRyrnXr19DLpfDzs5O5bydnR1u3bqVab8PHjzAsWPH0LNnTxw8eBD37t3DkCFDkJKSgqlTp2Y5PlZoiIiIdFT6Kid1DwBwcnKCpaWl8pg9e7ZGYlQoFLC1tcXKlStRtWpVdO3aFRMnTkRAQEC2+mGFhoiISFeJQrbnwGTaB4CIiAhYWFgoT39anQGAwoULQ09PD1FRUSrno6KiYG9vn2n3Dg4OMDAwgJ6envJcuXLlEBkZieTkZBgaGmYpTFZoiIiIdJQm59BYWFioHJklNIaGhqhatSpCQkKU5xQKBUJCQlCrVq1MY/Tw8MC9e/egUCiU5+7cuQMHB4csJzMAExoiIiLSID8/P6xatQrr16/HzZs3MXjwYCQkJChXPXl5eWH8+PHK9oMHD8abN28wfPhw3LlzBwcOHMCsWbPw448/Zuu+HHIiIiLSVVp4mFPXrl3x6tUrTJkyBZGRkahcuTKCgoKUE4WfPHkCmey/eoqTkxMOHz6MkSNHomLFiihSpAiGDx+OsWPHZuu+TGiIiIh0lLYefeDr6wtfX99M3wsNDc1wrlatWvjnn3+yfZ+PcciJiIiIJI8VGiIiIl2m7pCTRDChISIi0lF82jYRERGRhLBCQ0REpKu0sMpJW5jQEBER6Szh/w91+8j7OOREREREkscKDRERka7ikBMRERFJHhMaIiIikjwNPm07r+McGiIiIpI8VmiIiIh0lCimHer2IQUaq9DExMRoqisiIiLSBFFDhwTkKKGZO3cutm3bpnzdpUsXFCpUCEWKFMGVK1c0FhwRERFRVuQooQkICICTkxMAIDg4GMHBwTh06BCaN2+O0aNHazRAIiIiyqH0ScHqHhKQozk0kZGRyoRm//796NKlC5o0aQIXFxfUqFFDowESERFRzghi2qFuH1KQowqNtbU1IiIiAABBQUHw9PQEAIiiCLlcrrnoiIiIiLIgRxWaDh06oEePHihVqhSio6PRvHlzAMDly5dRsmRJjQZIREREOcSN9b5s0aJFcHFxQUREBObNmwczMzMAwIsXLzBkyBCNBkhEREQ5lI821stRQmNgYICffvopw/mRI0eqHRARERFRdmU5odm7d2+WO23Tpk2OgiEiIiIN4pBTRu3atctSO0EQODGYiIgoL2BCk5FCofiWcRAREZGm5aOERu1HHyQmJmoiDiIiIqIcy1FCI5fLMWPGDBQpUgRmZmZ48OABAGDy5MlYs2aNRgMkIiKiHMpHOwXnKKGZOXMmAgMDMW/ePBgaGirPu7m5YfXq1RoLjvKnQo7WGLt2MLY/XY59b9ZixYXZKFWluPL9n1YOwJEPG1WOmXvGaDFi+tTq9fGo6fkCjmUi4FgmAg1bR+LIsQ8AgDdv5fhp0hu4130OG9cIlPv+GUZPfoPYONVh7bDwJLTqEoWi5SLgVD4C7Xq8xLUbycr3Zy2IgXmRJxkOu5IRufpZKWdaDWqCFeHzsTtmPXbHrMeS0zPxfbPK2g5L56TvFKzuIQU5Wrb9xx9/YOXKlWjUqBEGDRqkPF+pUiXcunVLY8FR/mNmZYJFx6bgyt83MbHdL4h9FY8iJe3w7m2CSrsLh69g/sCVytcpSSm5HSp9gaODHqaNt4JrcX2IIrD5rwR06/MKpw/bQxSBF1FyzJxshbKlDRDxVI7h497gRWQ0Nq6yAQC8S1Cgfc9XaNGkABbOKgi5XMTM+bFo1/Mlbl0oAgMDAcMGWaBvL3OV+7bq+hJVKhlmFhLlMa+fRmPN+E14dvcFIAho4l0f03aPxeAqo/H436faDo8kKEcJzbNnzzLdEVihUCAlRTu/WAThyyWxqVOnwt/fP3eCoRzrMqo1Xj19gwUfJSuRj19laJeSnIK3UbG5GRplQ4smJiqvp46zwpoN73D+UjK8u5th0/8nLgBQwsUAU8daod+w10hNFaGvL+DOvRS8jVFg0k+WKFok7cfUeD9L1PSMxJOnqXAtbgAzUxnMTP+7x7Ubybh1JwWL51jnymck9fyzP0zl9bpJW9BqUBOUq1maCY0m5aNJwTlKaMqXL4+TJ0/C2dlZ5fz27dvh7u6ukcCy68WLF8r/3rZtG6ZMmYLbt28rz6XvZgz898wpff0cffxvKjk5WWUYL7+p1bIKwo5exaRNQ1GxTlm8fv4W+1YexaF1oSrtKtYthz8fL0V8zHuEh95A4LTtiH/zTjtB0xfJ5SJ27X+PhPcK1KhqlGmb2HgFzM1k0NdP+8OklKsBClrL8MfWd/hpqCXkchF/bElAmVL6cHbK/N/t+i3vULKEPjxqGH+zz0Lfhkwmww+da8LY1Aj/nr2j7XBIonI0h2bKlCnw9fXF3LlzoVAosHPnTvTv3x8zZ87ElClTNB1jltjb2ysPS0tLCIKgfH3r1i2Ym5vj0KFDqFq1KoyMjHDq1CkkJSVh2LBhsLW1hbGxMerUqYMLFy4o+wwMDISVlZXKfXbv3q1SDbpy5QoaNGgAc3NzWFhYoGrVqrh48aLy/VOnTqFu3booUKAAnJycMGzYMCQk/Dd84uLighkzZsDLywsWFhYYMGDAt/siSYBDcRu06t8Iz+5FYXybedi/KgRDFnihcc+6yjYXg69iXr8VGNNiNtZM2oqKdcth5p7RkMmkMXEtv7hxMxn2pSJQqHgERox7g82rbVC2tEGGdq/fyDFvcSx8ev73R4e5mQyHttti2873sHGNgH3ppwgO/YCdG22VSc/HEhNF/LnrPby6m2V4j/IuF7di2Bu3AQcTN2P48gGY1uEXPLnJ6gzlTI4SmrZt22Lfvn04evQoTE1NMWXKFNy8eRP79u1D48aNNR2jxowbNw5z5szBzZs3UbFiRYwZMwY7duzA+vXrcenSJZQsWRJNmzbFmzdvstxnz549UbRoUVy4cAFhYWEYN24cDAzSfmjfv38fzZo1Q8eOHXH16lVs27YNp06dgq+vr0of8+fPR6VKlXD58mVMnjw5wz2SkpIQFxencugqQSbD3fBHWDf1T9y/8hgH1x7HoXXH0bJ/Q2Wb0L/+wT8HLuHRjac4sy8MkzvMR9lqrqj4Q3ktRk6fKuVqgNNH7HF8vz36eplj4Iho3LqjOiQdF69AZ69XKFvaABNGWSrPf/igwI8/vUGNakY4ts8OwbvtUL6MATp5vcKHDxn3xNoX9B7x7xTo2dk0w3uUdz29/RyD3EdjaM0J2BdwBKMDfVGsXFFth6VTBGhgUrC2P0QW5XjMpW7duggODtZkLN/c9OnTlQlXQkICli9fjsDAQOXTwletWoXg4GCsWbMGo0ePzlKfT548wejRo1G2bFkAQKlSpZTvzZ49Gz179sSIESOU7/3666+oV68eli9fDmPjtNJ4w4YNMWrUqM/eY/bs2Zg2bVq2P68UvYmMwZObz1XOPbn1HHXaff/ZayIfvULMqzgUcbVDeOiNbx0iZZGhoQDX4mnJvXtFQ1wKT8Ky1fH4dV5BAED8OwXa93wJM1MBm1fbwMDgvx+bf+5+j8cRqQjZa6esvK1dWhhO5Z/iwJEP6NRWNXFZv/kdmnkWgK2NXi59OtKE1JRUPL8fCQC4e+kBylRzRfvhLbBk0MqvXElZlo8eTqnWxnoXL17Ehg0bsGHDBoSFhX39Ai2rVq2a8r/v37+PlJQUeHh4KM8ZGBigevXquHnzZpb79PPzQ79+/eDp6Yk5c+bg/v37yveuXLmCwMBAmJmZKY+mTZtCoVDg4cOHmcaVmfHjxyM2NlZ5RETo7rLUG2fvoGhpB5VzRUvZI+rJ689eU7hIQVgUMkN0ZMw3jo7UoVAASclpswvj4hVo2/0lDA0FbAu0gbGx6g/MDx9EyGTAx3P9019/umn5oyepOHEmCV7dONwkdYJMBkPDjMOSRFmRo4Tm6dOnqFu3LqpXr47hw4dj+PDh+P7771GnTh08fZp3xz9NTbNXjpbJZBBF1endn67i8vf3x40bN9CyZUscO3YM5cuXx65duwAA7969w8CBAxEeHq48rly5grt378LV1TXLcRkZGcHCwkLl0FU7fwtCuequ6Da6DRxL2KFB11po0acB9q04CgAwNjVC/1ndUba6K+yKFUbl+hUw7c+ReH4/CmHBV7UcPaWbOjsGp/5JxOOIVNy4mYyps2Nw8mwSunYwUSYz7z+IWDq/IOLjRUS9lCPqpRxyedq/twY/GCMmVgG/CW9x624Kbt5OxuCR0dDXB36orTrpd8PWd7C300OThpwMLCV9ZvXAd3XLwc7ZBi5uxdBnVg9Uql8eIZtPajs03SJq6JCAHA059evXDykpKbh58ybKlCkDALh9+zZ8fHzQr18/BAUFaTTIb8HV1RWGhoY4ffq0crVWSkoKLly4oBwisrGxQXx8PBISEpRJR3h4eIa+SpcujdKlS2PkyJHo3r071q1bh/bt26NKlSr4999/M13iTpm7E/YA07ouRp/pXfG/Ce0Q+egVlo/eiGNbzwAAFHIFirs5oXHPOjC1MkX0i7e4dPQaAqdvR0pyqpajp3SvXssxcHg0Il/KYWEug1s5A+zebIOGPxTAyTOJuHg5bYO8Sh4vVK67/o8jnJ30UaakAf4MtMHshXHwbBMJmUxAxQoG2LnRFvZ2/w0rKRQiNv2ZgJ6dTaGnJ42yOKWxsrXEmPW+KOhgjYTY93h49THGN5uJS0f5h4lGcdn2l/399984c+aMMpkBgDJlyuC3335D3bp1v3Bl3mFqaorBgwdj9OjRKFiwIIoVK4Z58+bh/fv36Nu3LwCgRo0aMDExwYQJEzBs2DCcO3cOgYGByj4+fPiA0aNHo1OnTihevDiePn2KCxcuoGPHjgCAsWPHombNmvD19UW/fv1gamqKf//9F8HBwfj999+18bEl4dyhcJw7FJ7pe8mJKZjQZl7uBkTZtmxBoc++V7e2MeKfFftqHw1/KICGPxT4YhuZTMCti0WyHR9p38J+y7UdQr6giZ1+pbJTcI6GnJycnDLdQE8ul8PR0VHtoHLLnDlz0LFjR/Tq1QtVqlTBvXv3cPjwYVhbp23MVbBgQWzcuBEHDx7Ed999hy1btqhszqenp4fo6Gh4eXmhdOnS6NKlC5o3b66cwFuxYkX8/fffuHPnDurWrQt3d3dMmTJFUl8jIiIiKRDETyeJZMGePXswa9YsLF26VDmh9eLFixg6dCjGjh2Ldu3aaTpO+khcXBwsLS3RwKgL9AVOoNN1O+9zTkF+0r5odW2HQLkgVUxBKPYgNjb2m8yLTP894fLzTMiM1ZtfpkhMxKNJE79ZrJqS5SEna2trlQ3lEhISUKNGDeVuu6mpqdDX10efPn2Y0BAREeUFnEOT0eLFi79hGEREREQ5l+WExtvb+1vGQURERBqWnyYFq/10xsTERCQnJ6ucy8tjbERERPkGdwr+soSEBPj6+sLW1hampqawtrZWOYiIiIhyU44SmjFjxuDYsWNYvnw5jIyMsHr1akybNg2Ojo74448/NB0jERER5QR3Cv6yffv24Y8//kD9+vXh4+ODunXromTJknB2dsamTZvQs2dPTcdJRERE2ZSf5tDkqELz5s0blChRAkDafJk3b94AAOrUqYMTJ05oLjoiIiKiLMhRQlOiRAnl06LLli2LP//8E0Ba5cbS0lJz0REREVHO5aMhpxwlND4+Prhy5QoAYNy4cVi6dCmMjY0xcuRIjBkzRqMBEhERUQ6J/w075fSQSkKTozk0I0eOVP63p6cnbt26hbCwMBQuXBgbN27UWHBERESkhny0U3COKjSfcnZ2RocOHWBpaYk1a9ZooksiIiKiLFN7Yz0iIiLKo/JRhYYJDRERkY7ism0iIiIiCclWhaZDhw5ffD8mJkadWIiIiIhyJFsJzdf2mLG0tISXl5daAREREZGGcA5N5tatW/et4iAiIiLKMU4KJiIi0lH5aVIwExoiIiJdJpGERF1c5URERESSxwoNERGRruKkYCIiIpI6zqEhIiIi6ctHFRrOoSEiIiLJY0JDRESko9KHnNQ9smvp0qVwcXGBsbExatSogfPnz2fpuq1bt0IQBLRr1y7b92RCQ0REpKtEDR3ZsG3bNvj5+WHq1Km4dOkSKlWqhKZNm+Lly5dfvO7Ro0f46aefULdu3ezd8P8xoSEiIiKNWbhwIfr37w8fHx+UL18eAQEBMDExwdq1az97jVwuR8+ePTFt2jSUKFEiR/dlQkNERKSrcrlCk5ycjLCwMHh6eirPyWQyeHp64uzZs5+9bvr06bC1tUXfvn2z8eFUcZUTERGRjtLksu24uDiV80ZGRjAyMlI59/r1a8jlctjZ2amct7Ozw61btzLt/9SpU1izZg3Cw8PVipMVGiIiIvoqJycnWFpaKo/Zs2er3Wd8fDx69eqFVatWoXDhwmr1xQoNERGRrtLgPjQRERGwsLBQnv60OgMAhQsXhp6eHqKiolTOR0VFwd7ePkP7+/fv49GjR2jdurXynEKhAADo6+vj9u3bcHV1zVKYrNAQERHpKg3OobGwsFA5MktoDA0NUbVqVYSEhCjPKRQKhISEoFatWhnaly1bFteuXUN4eLjyaNOmDRo0aIDw8HA4OTll+aOyQkNEREQa4+fnB29vb1SrVg3Vq1fH4sWLkZCQAB8fHwCAl5cXihQpgtmzZ8PY2Bhubm4q11tZWQFAhvNfw4SGiIhIR2njWU5du3bFq1evMGXKFERGRqJy5coICgpSThR+8uQJZDLNDxAxoSEiItJVWnqWk6+vL3x9fTN9LzQ09IvXBgYGZv+GYEJDRESks/LT07Y5KZiIiIgkjxUaIiIiXaWlISdtYEJDRESkq/JRQsMhJyIiIpI8VmiIiIh0lPD/h7p9SAETGiIiIl3FISciIiIi6WCFhoiISEflp31omNAQERHpKg45EREREUkHKzRERES6TCIVFnUxoSEiItJRnENDRERE0sc5NERERETSwQoNERGRjuKQExEREUkfh5yIiIiIpIMVGgkTk5IgCgpth0HfWPui1bUdAuWiw8/DtR0C5YK4eAWsS3/7+3DIiYiIiKSPQ05ERERE0sEKDRERka7KRxUaJjREREQ6inNoiIiISPryUYWGc2iIiIhI8lihISIi0lGCKEIQ1SuxqHt9bmFCQ0REpKs45EREREQkHazQEBER6SiuciIiIiLp45ATERERkXSwQkNERKSjOORERERE0schJyIiIiLpYIWGiIhIR3HIiYiIiKQvHw05MaEhIiLSYVKpsKiLc2iIiIhI8lihISIi0lWimHao24cEMKEhIiLSUflpUjCHnIiIiEjyWKEhIiLSVVzlRERERFInKNIOdfuQAg45ERERkeSxQkNERKSrOOREREREUsdVTkREREQSwgoNERGRruLGekRERCR1+WnIiQkNERGRrspHk4I5h4aIiIgkjxUaIiIiHcUhJyIiIpK+fDQpmENOREREJHms0BAREekoDjkRERGR9HGVExEREZF0sEJDRESkozjkRERERNKnENMOdfuQAA45ERERkeSxQkNERKSr8tGkYCY0REREOkqABubQaCSSb48JDRERka7iTsFEREREObN06VK4uLjA2NgYNWrUwPnz5z/bdtWqVahbty6sra1hbW0NT0/PL7b/HCY0REREOip92ba6R3Zs27YNfn5+mDp1Ki5duoRKlSqhadOmePnyZabtQ0ND0b17dxw/fhxnz56Fk5MTmjRpgmfPnmXrvkxoiIiIdJWooSMbFi5ciP79+8PHxwfly5dHQEAATExMsHbt2kzbb9q0CUOGDEHlypVRtmxZrF69GgqFAiEhIdm6LxMaIiIi0ojk5GSEhYXB09NTeU4mk8HT0xNnz57NUh/v379HSkoKChYsmK17c1IwERGRjhJEEYKak3rTr4+Li1M5b2RkBCMjI5Vzr1+/hlwuh52dncp5Ozs73Lp1K0v3Gzt2LBwdHVWSoqxghYaIiEhX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\n"
          },
          "metadata": {}
        }
      ]
    }
  ]
}
